package pt.isel.pdm.ouryamba;

import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import winterwell.jtwitter.Twitter;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;

public class TimelinePullService extends Service implements Runnable{

	private General app;
	private Thread worker_thread;
	private Thread updater_thread;
	public static final String ID = TimelinePullService.class.getName();
	private final String UPDATER_THREAD_NAME = ID + "_updater_thread";
	private final String WORKER_THREAD_NAME = ID + "_worker_thread";
	private final Integer DEFAULT_TOKEN_VALUE = -1;
	/**
	 * Is the argument that needs to be passed in the intent if the action desired is to start the 
	 * automatic updates.
	 * */
	public static final Integer ARG_START_AUTOMATIC_UPDATE = 1;
	/**
	 * Is the argument that needs to be passed in the intent if the action desired is to stop the 
	 * automatic updates.
	 * */
	public static final Integer ARG_STOP_AUTOMATIC_UPDATE = 2;
	/**
	 * Is the name of the argument that needs to be passed in the intent to id the action desired is
	 * related to automatic updates.
	 * */
	public static final String ARG_ACTION_TYPE = "type of action";
	private final BlockingQueue<Intent> queue = new LinkedBlockingQueue<Intent>();
	private final Integer CONST_ONE_MIN_IN_MILIS = 60000;
	private AtomicBoolean update;
	
	
//Service methods
	
	/**
	 * Since this is a started service, the is no object to bind.
	 * */
	@Override
	public IBinder onBind(Intent arg0)
	{
		return null;
	}

	/**
	 * Starts the background worker thread and initialize some variables.
	 * */
	@Override
	public void onCreate() 
	{
		super.onCreate();
		app = (General) getApplication();
		update = new AtomicBoolean(false);
		worker_thread = new Thread(new Runnable ()
		{
			/**
			 * Fetches the list of statuses through the worker thread.</br>
			 * 
			 * Uses the block mechanism from the {@code queue} variable to wait for work.
			 * Notify all waiters when the update is accomplished or when there are requests and the is no
			 * connectivity network. </br>
			 * 
			 * After fetching the list of new status from the server, remove all the status from the database
			 * with committed value true, and insert the new list.
			 * Also notifies that the list was updated so the notification system can do his work (show or not
			 * the notification in the notification bar).
			 * 
			 * @see General#hasNetworkConnection()
			 * @see General#log(String)
			 * @see Waiter#NotifyAll()
			 * @see General#getTwitter()
			 * @see General#dbDeleteByCommitedValue(boolean)
			 * @see General#dbInsertMany(List<Status>, boolean)
			 * @see General#timelineUpdated(long)
			 * @see General#logException(Exception)
			 * */
			@Override
			public void run() 
			{
				app.log("TimelinePullService onCreate");
				try
				{
					while(true)
					{
						queue.take();
						if(!app.hasNetworkConnection())
						{
							app.log("TimelinePullService.worker_thread.run(): Updates discarded because network unavailable.");
							app.waiter.NotifyAll();
							continue;
						}
						List<Twitter.Status> list = app.getTwitter().getUserTimeline();
						app.log("TimelinePullService.worker_thread.run(): Timeline fetched.");
						app.dbDeleteByCommitedValue(true);
						app.dbInsertMany(list, true);
						if(list.size() > 0)
						{
							app.timelineUpdated(list.get(0).id);
						}
						app.waiter.NotifyAll();
					}
				}
				catch (InterruptedException e)
				{
					app.log("TimelinePullService.worker_thread.run(): Thread interrupted.");
				}
				catch (Exception e)
				{
					app.log("TimelinePullService.worker_thread.run(): Exception thrown.");
					app.logException(e);
				}
			}
		});
		worker_thread.setName(WORKER_THREAD_NAME);
		worker_thread.start();
	}
	
	/**
	 * Depending to the argument sent in the intent, performs different actions in the service.</br>
	 * 
	 * The intent can have one argument to specify if the automatic updates are to stop or start.
	 * If that argument is not sent, then the intent is processed as a simple request to fetch new
	 * statuses from the server.
	 * 
	 * @see #ARG_START_AUTOMATIC_UPDATE
	 * @see #ARG_STOP_AUTOMATIC_UPDATE
	 * @see #ARG_ACTION_TYPE
	 * @return The flag START_NOT_STICKY which means the service should not be restarted after
	 * Android killed the service for memory usage.
	 * */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId)
	{
		int action = intent.getIntExtra(ARG_ACTION_TYPE, DEFAULT_TOKEN_VALUE);
		if(action == ARG_START_AUTOMATIC_UPDATE)
		{
			update.compareAndSet(false, true);
			if(updater_thread == null)
			{
				updater_thread = new Thread(this, UPDATER_THREAD_NAME);
				updater_thread.start();
			}
		}
		else if(action == ARG_STOP_AUTOMATIC_UPDATE)
		{
			update.compareAndSet(true, false);
			if(updater_thread != null)
			{
				updater_thread.interrupt();
				updater_thread = null;
			}
		}
		else
		{
			queue.add(intent);
		}
		return START_NOT_STICKY;
	}
	
	/**
	 * Stops the background worker thread and reference it to null (helps the GC), only if
	 * there is none network connectivity and the automatic updates are not enabled.
	 * */
	@Override
	public void onDestroy()
	{
		if(!app.hasNetworkConnection() && updater_thread == null)
		{
			worker_thread.interrupt();
			worker_thread = null;
		}
		super.onDestroy();
	}

	
//Runnable methods

	/**
	 * Is the behavior of the updater thread which is responsible for the automatic updates.</br>
	 * 
	 * This thread will wait until there is network connectivity so it wont waste resources.
	 * It only work according to the preference set in the preferences by the user.
	 * For each interval settled it send an intent to the worker thread so that one could perform
	 * the update and write into database (and also notify waiters).
	 * 
	 * @see General#hasNetworkConnection()
	 * @see General#log(String)
	 * @see General#getAutomaticUpdate()
	 * @see General#getAutomaticUpdateInterval()
	 * @see General#logException(Exception)
	 * */
	@Override
	public void run() 
	{
		app.log("TimelinePullService.updater_thread.run(): Update started.");
		TimelineActivity tma;
		Intent updaterIntent;
		int sleepTime;
		try
		{
			while(update.get())
			{
				while(!app.hasNetworkConnection())
				{
					synchronized (app.waiterForNetworkLock) 
					{
						app.log("TimelinePullService.updater_thread.run(): Going to wait for network.");
						app.waiterForNetworkLock.wait();
						app.log("TimelinePullService.updater_thread.run(): Waking with network.");
					}	
				}
				if(app.getAutomaticUpdate() && app.hasNetworkConnection())
				{
					updaterIntent = new Intent(getApplicationContext(), TimelinePullService.class);
					tma = app.timelineActivity.get();
					if(tma != null && tma.hasWindowFocus())
					{
						app.waiter.Wait(tma, tma);
					}
					onStartCommand(updaterIntent, 0, 0);
					sleepTime = app.getAutomaticUpdateInterval()*CONST_ONE_MIN_IN_MILIS;
					SystemClock.sleep(sleepTime);
				}
			}
		}
		catch(InterruptedException e)
		{
			app.log("TimelinePullService.updater_thread.run(): Thread interrupted.");
		}
		catch(Exception e)
		{
			app.log("TimelinePullService.updater_thread.run(): Exception thrown.");
			app.logException(e);
		}
		app.log("TimelinePullService.updater_thread.run(): Update stopped.");
	}

}
 